package p.ithorns.tools.translator.apt.processor;

import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
import p.ithorns.tools.translator.annotation.meta.DictGen;
import p.ithorns.tools.translator.annotation.DictType;
import p.ithorns.tools.translator.apt.BaseProcessor;
import p.ithorns.tools.translator.apt.creator.FieldCreator;
import p.ithorns.tools.translator.apt.creator.GetterCreator;
import p.ithorns.tools.translator.apt.creator.SetterCreator;
import p.ithorns.tools.translator.apt.utils.AptUtil;
import p.ithorns.tools.translator.apt.utils.EnterReflect;
import p.ithorns.tools.translator.apt.utils.MarkingScanner;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * DictClassProcessor
 * <p>
 * 从元注解复制
 *
 * @author Ht.L
 * @date 2024-05-30 23:22
 * @since 1.0.0
 */
@AutoService(Processor.class)
public class DictClassProcessor extends BaseProcessor {

    /**
     * 类词典生成注解类
     */
    private static final Class<? extends Annotation> GEN_TYPE = DictGen.class;


    private GetterCreator getterCreator;

    private SetterCreator setterCreator;

    private FieldCreator fieldCreator;

    public DictClassProcessor() {
        // 显式声明
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        try {
            getterCreator = new GetterCreator(this);
            setterCreator = new SetterCreator(this);
            fieldCreator = new FieldCreator(this);
        } catch (Exception e) {
            error("DictProcessor init error.");
        }
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_17;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(GEN_TYPE.getName());
    }


    /**
     * @param annotations the annotation interfaces requested to be processed
     * @param roundEnv    environment for information about the current and prior round
     * @return 返回值表示这些注解是否由此 Processor 声明
     * 如果返回 true，则这些注解不会被后续 Processor 处理；
     * 如果返回 false，则这些注解可以被后续的 Processor 处理。
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver()) {
            return false;
        }

        // 明确注解类型
        Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(GEN_TYPE);
        for (Element element : annotatedElements) {
            final JCTree tree = trees.getTree(element);
            tree.accept(new TreeTranslator() {
                @Override
                public void visitClassDef(JCClassDecl jcClass) {
                    AptUtil.removeAnnotationIfNecessary(jcClass, GEN_TYPE.getName());
                    String className = jcClass.getSimpleName().toString();
                    //过滤属性
                    Map<Name, JCVariableDecl> varMembers = getVarMembers(jcClass);
                    for (Map.Entry<Name, JCVariableDecl> entry : varMembers.entrySet()) {
                        JCVariableDecl source = entry.getValue();
                        // 检查元注解重复
                        if (checkIfDuplicate(source)) {
                            return;
                        }

                        JCVariableDecl newField = fieldCreator.create(jcClass, source);
                        if (null == newField) {
                            return;
                        }

                        // 移除原字段上的源注解
                        deleteMetaAnnotation(source);

                        jcClass.defs = jcClass.defs.append(newField);
                        EnterReflect.memberEnter(context, newField, tree);

                        // Getter
                        String getterName = makeCamelSignature(newField.getName(), "get");
                        if (AptUtil.notExistsMethod(jcClass, getterName, true, 0)) {
                            JCTree.JCMethodDecl getter = getterCreator.create(newField);
                            jcClass.defs = jcClass.defs.append(getter);
                            EnterReflect.memberEnter(context, getter, tree);
                        }

                        // Setter
                        String setterName = makeCamelSignature(newField.getName(), "set");
                        if (AptUtil.notExistsMethod(jcClass, setterName, true, 1)) {
                            JCTree.JCMethodDecl setter = setterCreator.create(newField);

                            // 解决Compile failed: initParam AssertError的关键点
                            setter.accept(new MarkingScanner(context, jcClass));
                            jcClass.defs = jcClass.defs.append(setter);
                            EnterReflect.memberEnter(context, setter, tree);
                        }
                    }

                    info("[DictProcessor]-> Process [{}] end!", className);
                    super.visitClassDef(jcClass);
                }
            });
        }
        return true;
    }


    private String makeCamelSignature(Name name, String prefix) {
        return AptUtil.makeCamelName(prefix, name.toString());
    }

    private Map<Name, JCVariableDecl> getVarMembers(JCClassDecl jcClass) {
        return jcClass.getMembers().stream()
                .filter(tree -> Tree.Kind.VARIABLE.equals(tree.getKind()))
                .map(tree -> (JCVariableDecl) tree)
                .filter(this::checkIfContainsAnnotation)
                .collect(Collectors.toMap(JCVariableDecl::getName, Function.identity()));
    }

    private void deleteMetaAnnotation(final JCVariableDecl varDecl) {
        final Set<String> metaClasses = DictType.getMetaClassNames();
        List<JCTree.JCAnnotation> annotations = varDecl.getModifiers().getAnnotations();
        ListBuffer<JCAnnotation> collect = annotations.stream()
                .filter(annotation -> {
                    String atn = AptUtil.getJcAnnotationTypeName(annotation);
                    return !metaClasses.contains(atn);
                })
                .collect(Collectors.toCollection(ListBuffer::new));
        varDecl.mods.annotations = collect.toList();
    }


    private boolean checkIfContainsAnnotation(JCVariableDecl varDecl) {
        List<JCTree.JCAnnotation> annotations = varDecl.getModifiers().getAnnotations();
        Set<String> metaClasses = DictType.getMetaClassNames();
        for (JCTree.JCAnnotation annotation : annotations) {
            // 从JCAnnotation 获取 metaClass 类型
            String atn = AptUtil.getJcAnnotationTypeName(annotation);
            if (metaClasses.contains(atn)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检查该元素是否出现重复元注解
     * 例如标注了@DictMeta还标注了@EnumField, 避免导致冲突
     */
    private boolean checkIfDuplicate(JCVariableDecl varDecl) {
        // 获取varDecl上所有注解
        List<JCAnnotation> annotations = varDecl.getModifiers().getAnnotations();
        Set<String> annotationTypes = annotations.stream()
                .map(JCAnnotation::getAnnotationType)
                .map(Object::toString)
                .collect(Collectors.toSet());

        for (DictType t : DictType.values()) {
            String annotationName = t.getOriginClass().getName();
            if (annotationTypes.contains(annotationName)) {
                error("[DictProcessor]-> Duplicate @annotation found on field:  @{}", annotationName);
                return true;
            }
        }

        return false;
    }

}